package cn.mw.cmdb.util;

import cn.mw.cmdb.enums.DateUnitEnum;
import cn.mw.components.mongodb.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;

@Slf4j
public class CronUtil {
    public static final String SPACE = " ";

    /**
     * 在开始时间与结束时间区间内，根据时间间隔进行cron表达式生成
     *
     * @param startTime 2023-04-11 13:10:10
     * @param endTime   2023-04-29 13:23:20
     * @param interval  30
     * @param dateUnit  min
     * @return cron表达式：0 10/30 13 05-12 4 * 2024
     */
    public static String generateCronExpressionByDateRange(LocalDateTime startTime, LocalDateTime endTime,
                                                           int interval, DateUnitEnum dateUnit) {
        // 1. 判断时间单位的类型
        if (!dateUnit.verify(interval)) {
            throw new BusinessException("calculation-period-error");
        }
        String ret;
        switch (dateUnit) {
            case MINUTE -> ret = generateCronByMinute(startTime, endTime, interval);
            case HOUR -> ret = generateCronByHour(startTime, endTime, interval);
            case DAY -> ret = generateCronByDay(startTime, endTime, interval);
            case WEEK -> ret = generateCronByWeek(startTime, endTime, interval);
            case MONTH -> ret = generateCronByMonth(startTime, endTime, interval);
            case YEAR -> ret = generateCronByYear(startTime, endTime, interval);
            default -> ret = "";
        }
        return ret;
    }


    private static String generateCronByYear(LocalDateTime startTime, LocalDateTime endTime, int interval) {
        // 获取年
        if (compareYear(startTime, endTime)) {
            throw new RuntimeException("error year");
        }
        String cronYear = startTime.getYear() + "-" + endTime.getYear() + "/" + interval;

        String cronWeek = "?";
        String cronMonth = compareMonth(startTime, endTime) ? String.valueOf(startTime.getMonthValue())
                : startTime.getMonthValue() + "-" + endTime.getMonthValue();
        String cronDay = compareDay(startTime, endTime) ? String.valueOf(startTime.getDayOfMonth())
                : startTime.getDayOfMonth() + "-" + endTime.getDayOfMonth();
        String cronHour = compareHour(startTime, endTime) ? String.valueOf(startTime.getHour())
                : startTime.getHour() + "-" + endTime.getHour();
        String cronMinute = compareMinute(startTime, endTime) ? String.valueOf(startTime.getMinute())
                : startTime.getMinute() + "-" + endTime.getMinute();
        String cronSecond = String.valueOf(startTime.getSecond());
        String cronSb = cronSecond + SPACE + cronMinute + SPACE +
                cronHour + SPACE + cronDay + SPACE + cronMonth + SPACE +
                cronWeek + SPACE + cronYear;
        return cronSb;
    }


    private static String generateCronByMonth(LocalDateTime startTime, LocalDateTime endTime, int interval) {
        // 获取年
        String cronYear = compareYear(startTime, endTime) ? String.valueOf(startTime.getYear())
                : startTime.getYear() + "-" + endTime.getYear();
        String cronWeek = "?";
        if (compareMonth(startTime, endTime)) {
            throw new RuntimeException("error month");
        }
        String cronMonth = startTime.getMonthValue() + "-" + endTime.getMonthValue() + "/" + interval;
        String cronDay = compareDay(startTime, endTime) ? String.valueOf(startTime.getDayOfMonth())
                : startTime.getDayOfMonth() + "-" + endTime.getDayOfMonth();
        String cronHour = compareHour(startTime, endTime) ? String.valueOf(startTime.getHour())
                : startTime.getHour() + "-" + endTime.getHour();
        String cronMinute = compareMinute(startTime, endTime) ? String.valueOf(startTime.getMinute())
                : startTime.getMinute() + "-" + endTime.getMinute();
        String cronSecond = String.valueOf(startTime.getSecond());
        String cronSb = cronSecond + SPACE + cronMinute + SPACE +
                cronHour + SPACE + cronDay + SPACE + cronMonth + SPACE +
                cronWeek + SPACE + cronYear;
        return cronSb;
    }

    /**
     * @param startTime
     * @param endTime
     * @param interval
     * @return
     */
    private static String generateCronByWeek(LocalDateTime startTime, LocalDateTime endTime, int interval) {
        // 0 10 13 11-29 4 */4 2024
        // 获取年
        String cronYear = compareYear(startTime, endTime) ? String.valueOf(startTime.getYear())
                : startTime.getYear() + "-" + endTime.getYear();
        String cronWeek = "*/" + interval;
        String cronMonth = compareMonth(startTime, endTime) ? String.valueOf(startTime.getMonthValue())
                : startTime.getMonthValue() + "-" + endTime.getMonthValue();
        String cronDay = compareDay(startTime, endTime) ? String.valueOf(startTime.getDayOfMonth())
                : startTime.getDayOfMonth() + "-" + endTime.getDayOfMonth();
        String cronHour = compareHour(startTime, endTime) ? String.valueOf(startTime.getHour())
                : startTime.getHour() + "-" + endTime.getHour();
        String cronMinute = compareMinute(startTime, endTime) ? String.valueOf(startTime.getMinute())
                : startTime.getMinute() + "-" + endTime.getMinute();
        String cronSecond = String.valueOf(startTime.getSecond());
        String cronSb = cronSecond + SPACE + cronMinute + SPACE +
                cronHour + SPACE + cronDay + SPACE + cronMonth + SPACE +
                cronWeek + SPACE + cronYear;
        return cronSb;
    }

    /**
     * 例子4：
     * 时间区间：2023-04-11 13:10:10 - 2023-04-29 13:23:20
     * 时间间隔：4
     * 时间单位：day
     * 表达式：0 10 13 11/4 4 * 2024
     * 结果：
     * ...
     * 2024-04-11 13:10:00
     * 2024-04-15 13:10:00
     * 2024-04-19 13:10:00
     * ...
     *
     * @return 0 10 13 11-20/4 4 ? 2024
     */
    private static String generateCronByDay(LocalDateTime startTime, LocalDateTime endTime, int interval) {
        // 获取年
        String cronYear = compareYear(startTime, endTime) ? String.valueOf(startTime.getYear())
                : startTime.getYear() + "-" + endTime.getYear();
        String cronWeek = "?";
        String cronMonth = compareMonth(startTime, endTime) ? String.valueOf(startTime.getMonthValue())
                : startTime.getMonthValue() + "-" + endTime.getMonthValue();
        String cronDay = startTime.getDayOfMonth() + "-" + endTime.getDayOfMonth() + "/" + interval;
        // 自定义小时
        String cronHour = String.valueOf(startTime.getHour());
        String cronMinute = String.valueOf(startTime.getMinute());
        String cronSecond = String.valueOf(startTime.getSecond());
        String cronSb = cronSecond + SPACE + cronMinute + SPACE +
                cronHour + SPACE + cronDay + SPACE + cronMonth + SPACE +
                cronWeek + SPACE + cronYear;
        return cronSb;
    }

    /**
     * 根据小时生成时间cron: 从什么时候开始，每隔多少小时执行一次
     * 时间区间：2023-04-11 13:10:10 - 2023-04-29 13:23:20
     * 时间间隔：4
     * 时间单位：hour
     * 秒 分 [开始小时/间隔小时] 天 月 周 年
     * 表达式：0 10 13/4 11-29 4 * 2024
     * 结果：
     * ...
     * 2024-04-11 13:10:00
     * 2024-04-11 17:10:00
     * 2024-04-11 21:10:00
     * 2024-04-12 13:10:00
     * 2024-04-12 17:10:00
     */
    private static String generateCronByHour(LocalDateTime startTime, LocalDateTime endTime, int interval) {
        // 获取年
        String cronYear = compareYear(startTime, endTime) ? String.valueOf(startTime.getYear())
                : startTime.getYear() + "-" + endTime.getYear();
        String cronWeek = "?";
        String cronMonth = compareMonth(startTime, endTime) ? String.valueOf(startTime.getMonthValue())
                : startTime.getMonthValue() + "-" + endTime.getMonthValue();
        String cronDay = compareDay(startTime, endTime) ? String.valueOf(startTime.getDayOfMonth())
                : startTime.getDayOfMonth() + "-" + endTime.getDayOfMonth();
        // 自定义小时
        String cronHour = startTime.getHour() + "/" + interval;
        String cronMinute = String.valueOf(startTime.getMinute());
        String cronSecond = String.valueOf(startTime.getSecond());
        String cronSb = cronSecond + SPACE + cronMinute + SPACE +
                cronHour + SPACE + cronDay + SPACE + cronMonth + SPACE +
                cronWeek + SPACE + cronYear;
        return cronSb;
    }

    /**
     * 根据分钟生成时间cron: 从什么时候开始，每个多少分钟执行一次
     * 秒  分   时   天  月 周 年
     *
     * @return 0 10/30 13 05-12 4  * 2024
     */
    private static String generateCronByMinute(LocalDateTime startTime, LocalDateTime endTime, int interval) {
        // 获取年
        String cronYear = compareYear(startTime, endTime) ? String.valueOf(startTime.getYear())
                : startTime.getYear() + "-" + endTime.getYear();
        String cronWeek = "?";
        String cronMonth = compareMonth(startTime, endTime) ? String.valueOf(startTime.getMonthValue())
                : startTime.getMonthValue() + "-" + endTime.getMonthValue();
        String cronDay = compareDay(startTime, endTime) ? String.valueOf(startTime.getDayOfMonth())
                : startTime.getDayOfMonth() + "-" + endTime.getDayOfMonth();
        String cronHour = "*";
        // 分钟的计算
        String cronMinute = startTime.getMinute() + "/" + interval;
        String cronSecond = String.valueOf(startTime.getSecond());
        String cronSb = cronSecond + SPACE + cronMinute + SPACE +
                cronHour + SPACE + cronDay + SPACE + cronMonth + SPACE +
                cronWeek + SPACE + cronYear;
        return cronSb;
    }


    /**
     * 生成【activeStartTime, activeEndTime】这段时间范围内executeTime时间点执行的cron表达式
     *
     * @param activeStartTime 任务开始生效时间
     * @param activeEndTime   任务结束生效时间
     * @param executeTime     任务开始执行时间 格式 hour:minute:second
     *                        case:
     *                        入参: activeStartTime = 2023-08-21, activeEndTime = 2024-01-10, executeTime = 17:04:00
     *                        出参: 从2023-08-21至2024-01-10每天的17:04:00都会执行的cron表达式
     */
    public static String generateCronExpressionString(Date activeStartTime, Date activeEndTime, Date executeTime) {
        LocalDate start = LocalDates.toLocalDate(activeStartTime);
        LocalDate end = LocalDates.toLocalDate(activeEndTime);
        LocalDateTime localDateTime = LocalDates.toLocalDateTime(executeTime);
        LocalTime time = null;
        if (localDateTime != null) {
            time = localDateTime.toLocalTime();
        }
        return generateCronExpressionString(start, end, time);
    }

    public static String generateCronExpressionString(String activeStartTime, String activeEndTime, String executeTime) {
        LocalDate start = LocalDates.parseLocalDate(activeStartTime);
        LocalDate end = LocalDates.parseLocalDate(activeEndTime);
        LocalTime time = LocalDates.parseLocalTime(executeTime);
        return generateCronExpressionString(start, end, time);
    }

    public static String generateCronExpressionString(LocalDate startDate, LocalDate endDate, LocalTime time) {
        List<String> cronList = generateCronExpression(startDate, endDate, time);
        StringBuilder cronExpress = new StringBuilder();
        for (String cron : cronList) {
            cronExpress.append(cron).append(",");
        }
        if (cronExpress.length() > 0) {
            cronExpress.deleteCharAt(cronExpress.length() - 1);
        }
        return cronExpress.toString();
    }


    /**
     * 生成每天指定时间执行的cron表达式
     *
     * @param executeTime 任务开始执行时间 格式 hour:minute:second
     *                    case:
     *                    入参: executeTime = 17:04:00
     *                    出参: 每天的17:04:00都会执行的cron表达式
     */
    public static String generateCronExpression(String executeTime) {
        LocalTime time = LocalDates.parseLocalTime(executeTime);
        if (time != null) {
            return String.format("%d %d %d * * ? *", time.getSecond(), time.getMinute(), time.getHour());
        }
        throw new RuntimeException();
    }


    public static List<String> generateCronExpression(LocalDate startDate, LocalDate endDate, LocalTime time) {
        if (startDate.isAfter(endDate)) {
            throw new RuntimeException("参数异常:开始时间不能小于结束时间");
        }
        String timeCron = String.format("%d %d %d", time.getSecond(), time.getMinute(), time.getHour());

        List<String> cronList = new ArrayList<>();
        String firstCron = firstCron(startDate, endDate, timeCron);
        List<String> middleCronList = middleCron(startDate, endDate, timeCron);
        String lastCron = lastCron(startDate, endDate, timeCron);

        if (StringUtils.isNotBlank(firstCron)) {
            cronList.add(firstCron);
        }
        if (middleCronList != null && middleCronList.size() > 0) {
            cronList.addAll(middleCronList);
        }
        if (StringUtils.isNotBlank(lastCron)) {
            cronList.add(lastCron);
        }
        return cronList;
    }

    /**
     * 无论什么范围的时间, 最多可由5个cron表达式表达
     */
    public static List<String> generateCronExpression(String activeStartTime, String activeEndTime, String executeTime) {
        LocalDate start = LocalDates.parseLocalDate(activeStartTime);
        LocalDate end = LocalDates.parseLocalDate(activeEndTime);
        LocalTime time = LocalDates.parseLocalTime(executeTime);
        return generateCronExpression(start, end, time);
    }

    private static String firstCron(LocalDate startDate, LocalDate endDate, String timeCron) {
        int startYear = startDate.getYear();
        int startMonth = startDate.getMonthValue();
        int startDay = startDate.getDayOfMonth();

        int endYear = endDate.getYear();
        int endMonth = endDate.getMonthValue();
        int endDay = endDate.getDayOfMonth();

        // 同年同月直接返回
        if (endYear == startYear && startMonth == endMonth) {
            String dayRange = startDay == endDay ? String.valueOf(startDay) : String.format("%d-%d", startDay, endDay);
            return String.format("%s %s %d ? %d", timeCron, dayRange, startMonth, startYear);
        }
        int maxDayOfMouth = startDate.lengthOfMonth();
        String dayRange = startDay == maxDayOfMouth ? String.valueOf(startDay) : String.format("%d-%d", startDay, maxDayOfMouth);
        return String.format("%s %s %d ? %d", timeCron, dayRange, startMonth, startYear);
    }


    private static List<String> middleCron(LocalDate startDate, LocalDate endDate, String timeCron) {
        int startYear = startDate.getYear();
        int startMonth = startDate.getMonthValue();

        int endYear = endDate.getYear();
        int endMonth = endDate.getMonthValue();

        if (startYear == endYear) {
            if (startMonth + 1 >= endMonth) {
                return null;
            }
            // 同年月份相差1以上 2023-09-07 ~ 2023-12-21
            String monthRange = startMonth + 1 == endMonth - 1 ? String.valueOf(startMonth + 1) : String.format("%d-%d", startMonth + 1, endMonth - 1);
            return Collections.singletonList(String.format("%s * %s ? %d", timeCron, monthRange, startYear));
        }
        // 不同年
        List<String> middleCronList = new ArrayList<>(3);
        int maxMonthOfYear = 12;
        if (startMonth < maxMonthOfYear) {
            String monthRange = startMonth + 1 == maxMonthOfYear ? "12" : String.format("%d-12", startMonth + 1);
            middleCronList.add(String.format("%s * %s ? %d", timeCron, monthRange, startYear));
        }
        String middleYearCron = middleYearCron(startDate, endDate, timeCron);
        if (StringUtils.isNotBlank(middleYearCron)) {
            middleCronList.add(middleYearCron);
        }
        if (endMonth > 1) {
            String monthRange = endMonth - 1 == 1 ? "1" : String.format("1-%d", endMonth - 1);
            middleCronList.add(String.format("%s * %s ? %d", timeCron, monthRange, endYear));
        }
        return middleCronList;
    }


    private static String middleYearCron(LocalDate startDate, LocalDate endDate, String timeCron) {
        int startYear = startDate.getYear();
        int endYear = endDate.getYear();
        // 相差至少2年以上
        if (startYear == endYear || startYear + 1 == endYear) {
            return null;
        }
        String yearRange = startYear + 1 == endYear - 1 ? String.valueOf(startYear + 1) : String.format("%d-%d", startYear + 1, endYear - 1);
        return String.format("%s * * ? %s", timeCron, yearRange);
    }

    private static String lastCron(LocalDate startDate, LocalDate endDate, String timeCron) {
        int startYear = startDate.getYear();
        int startMonth = startDate.getMonthValue();

        int endYear = endDate.getYear();
        int endMonth = endDate.getMonthValue();
        int endDay = endDate.getDayOfMonth();
        // 同年同月直接返回
        if (endYear == startYear && startMonth == endMonth) {
            return null;
        }
        String dayRange = endDay == 1 ? "1" : String.format("1-%d", endDay);
        return String.format("%s %s %d ? %d", timeCron, dayRange, endMonth, endYear);
    }

    public static boolean compareYear(LocalDateTime startTime, LocalDateTime endTime) {
        return startTime.getYear() == endTime.getYear();
    }

    public static boolean compareMonth(LocalDateTime startTime, LocalDateTime endTime) {
        return startTime.getMonth() == endTime.getMonth();
    }

    public static boolean compareDay(LocalDateTime startTime, LocalDateTime endTime) {
        return startTime.getDayOfMonth() == endTime.getDayOfMonth();
    }

    public static boolean compareHour(LocalDateTime startTime, LocalDateTime endTime) {
        return startTime.getHour() == endTime.getHour();
    }

    public static boolean compareMinute(LocalDateTime startTime, LocalDateTime endTime) {
        return startTime.getMinute() == endTime.getMinute();
    }

    public static boolean compareSecond(LocalDateTime startTime, LocalDateTime endTime) {
        return startTime.getSecond() == endTime.getSecond();
    }

    public static void main(String[] args) {
        // Example usage:
        LocalDateTime startTime = LocalDateTime.of(2024, 5, 1, 8, 10, 0);
        LocalDateTime endTime = LocalDateTime.of(2024, 5, 3, 8, 10, 0);
        log.info("MINUTE : {}", CronUtil.generateCronExpressionByDateRange(startTime, endTime, 30, DateUnitEnum.MINUTE));
        log.info("HOUR : {}", CronUtil.generateCronExpressionByDateRange(startTime, endTime, 10, DateUnitEnum.HOUR));
        log.info("DAY : {}", CronUtil.generateCronExpressionByDateRange(startTime, endTime, 1, DateUnitEnum.DAY));
        endTime = LocalDateTime.of(2024, 8, 11, 8, 0, 0);
        log.info("MONTH : {}", CronUtil.generateCronExpressionByDateRange(startTime, endTime, 1, DateUnitEnum.MONTH));
        endTime = LocalDateTime.of(2025, 8, 11, 8, 0, 0);
        log.info("YEAR : {}", CronUtil.generateCronExpressionByDateRange(startTime, endTime, 1, DateUnitEnum.YEAR));
        endTime = LocalDateTime.of(2024, 6, 11, 8, 0, 0);
        log.info("WEEK : {}", CronUtil.generateCronExpressionByDateRange(startTime, endTime, 3, DateUnitEnum.WEEK));

    }
}
